package com.zdsx.config;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

@Component
@Slf4j(topic = "request.log")
@Order(1)
public final class LogFilter extends OncePerRequestFilter {

    @Value("${server.servlet.context-path}")
    private String contextPath;

    @Override
    protected boolean shouldNotFilterAsyncDispatch() {
        return false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 白名单的路径不做处理
        String fullUri = request.getRequestURI();
        String contextPath = request.getContextPath();
        String uriWithoutContextPath = fullUri;
        if (!contextPath.isEmpty()) {
            uriWithoutContextPath = fullUri.substring(contextPath.length());
        }
        if (writeList(String.valueOf(uriWithoutContextPath))) {
            filterChain.doFilter(request, response);
            return;
        }
        long cost = 0;
        HttpServletRequest requestToUse = request;
        if (!isAsyncDispatch(request) && !(request instanceof ContentCachingRequestWrapper)) {
            requestToUse = new ContentCachingRequestWrapper(request);
        }
        HttpServletResponse responseToUse = response;
        if (!isAsyncDispatch(request) && !(response instanceof ContentCachingResponseWrapper)) {
            responseToUse = new ContentCachingResponseWrapper(response);
        }
        try {
            StopWatch watch = new StopWatch();
            watch.start();
            filterChain.doFilter(requestToUse, responseToUse);
            watch.stop();
            cost = watch.getTotalTimeMillis();
        } finally {
            if (!isAsyncStarted(requestToUse)) {
                LogRequest logRequest = new LogRequest((ContentCachingRequestWrapper) requestToUse);
                LogResponse logResponse = new LogResponse((ContentCachingResponseWrapper) responseToUse);
                boolean isJson = JSONUtil.isJson(logResponse.getPayload());
                // 根据自己的情况，判断什么时候需要打印日志，我是把错误的请求日志才打印出来
                if (logResponse.getStatus() != 200 || (isJson && JSONUtil.parseObj(logResponse.getPayload()).getInt("code", 1001) != 200)) {
                    log.info(logFormatter(logRequest, logResponse, cost));
                }
                ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(responseToUse, ContentCachingResponseWrapper.class);
                if (wrapper != null) {
                    wrapper.copyBodyToResponse();
                }
            }
        }
    }

    private String logFormatter(LogRequest request, LogResponse response, long spendTime) {
        JsonStringBuilder jsonBuilder = new JsonStringBuilder(2000);
        jsonBuilder.startObject()
                .field("timestamp", LocalDateTime.now().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_ZONED_DATE_TIME))
                .field("uri", request.getRequestURI())
                .field("method", request.getMethod())
                .field("refer", request.getRemoteAddr())
                .field("status", response.getStatus())
                .field("time", spendTime + "ms")
                .startObject("request")
                .fieldRawValue("body", request.getPayload())
                .field("param", request.getParameters())
                .field("header", request.getHeaders())
                .endObject()
                .fieldRawValue("response", response.getPayload())
                .endObject();
        return jsonBuilder.build();
    }

    private boolean writeList(String path) {
        return path.equals("/") ||
                path.startsWith("/actuator") ||
                path.endsWith("/doc.html") ||
                path.startsWith("/plumelog") ||
                path.startsWith("/swagger") ||
                path.startsWith("/doc.html") ||
                path.startsWith("/swagger-resources") ||
                path.startsWith("/v2/api-docs") ||
                path.equals("/swagger-ui.html") ||
                path.startsWith("/webjars");
    }


}
